import { RestApiService } from './rest-api.service';
import { KlineUtilsService } from './kline-utils.service';

/**
 * K线数据的时间粒度
 */
export enum KlineBar {
  // 常规时间粒度
  SECOND_1 = '1s',
  MINUTE_1 = '1m',
  MINUTE_3 = '3m',
  MINUTE_5 = '5m',
  MINUTE_15 = '15m',
  MINUTE_30 = '30m',
  HOUR_1 = '1H',
  HOUR_2 = '2H',
  HOUR_4 = '4H',

  // 香港时间开盘价K线
  HOUR_6 = '6H',
  HOUR_12 = '12H',
  DAY_1 = '1D',
  DAY_2 = '2D',
  DAY_3 = '3D',
  WEEK_1 = '1W',
  MONTH_1 = '1M',
  MONTH_3 = '3M',

  // UTC时间开盘价K线
  HOUR_6_UTC = '6Hutc',
  HOUR_12_UTC = '12Hutc',
  DAY_1_UTC = '1Dutc',
  DAY_2_UTC = '2Dutc',
  DAY_3_UTC = '3Dutc',
  WEEK_1_UTC = '1Wutc',
  MONTH_1_UTC = '1Mutc',
  MONTH_3_UTC = '3Mutc'
}

/**
 * 历史K线请求参数
 */
export interface HistoricalKlineParams {
  instId: string;          // 产品ID，如 BTC-USDT
  after?: string;          // 请求此时间戳之前（更旧的数据）的分页内容
  before?: string;         // 请求此时间戳之后（更新的数据）的分页内容
  bar?: KlineBar | string; // 时间粒度，默认值1m
  limit?: number | string; // 分页返回的结果集数量，最大为300，不填默认返回100条
}

/**
 * K线数据项
 * 数组顺序为：[ts, o, h, l, c, vol, volCcy, volCcyQuote, confirm]
 */
export type KlineData = [
  string, // ts: 开始时间，Unix时间戳的毫秒数格式
  string, // o: 开盘价格
  string, // h: 最高价格
  string, // l: 最低价格
  string, // c: 收盘价格
  string, // vol: 交易量，以张为单位
  string, // volCcy: 交易量，以币为单位
  string, // volCcyQuote: 交易量，以计价货币为单位
  string  // confirm: K线状态，0：未完结，1：已完结
];

/**
 * 格式化的K线数据项
 */
export interface FormattedKlineData {
  timestamp: number;    // 开始时间，Unix时间戳的毫秒数格式
  open: number;         // 开盘价格
  high: number;         // 最高价格
  low: number;          // 最低价格
  close: number;        // 收盘价格
  volume: number;       // 交易量，以张为单位
  volumeCcy: number;    // 交易量，以币为单位
  volumeCcyQuote: number; // 交易量，以计价货币为单位
  confirmed: boolean;   // K线状态，false: 未完结，true: 已完结
}

/**
 * API 响应结构
 */
export interface ApiResponse<T> {
  code: string;
  msg: string;
  data: T;
}

/**
 * 市场数据服务配置选项
 */
export interface MarketDataServiceOptions {
  timeout?: number;
  proxy?: string;
  headers?: Record<string, string>;
}

/**
 * 市场数据服务
 * 提供获取市场数据的方法，如K线数据、行情数据等
 */
export class MarketDataService extends RestApiService {
  private klineUtilsService: KlineUtilsService;

  /**
   * 构造函数
   * @param baseUrl API基础URL，默认使用配置中的REST_URL
   * @param options 配置选项，包括超时时间、代理等
   */
  constructor(baseUrl?: string, options: MarketDataServiceOptions = {}) {
    // 处理null或undefined代理值已经在父类中完成，这里不需要重复处理
    super(baseUrl, options);
    this.klineUtilsService = new KlineUtilsService();

    console.log(`REST API基础URL: ${this.baseUrl}`);
  }

  /**
   * 测试与API服务器的连接
   * @returns Promise 包含测试结果
   */
  async testConnection(): Promise<{ success: boolean; message: string }> {
    try {
      // 尝试获取服务器时间作为连接测试
      const endpoint = '/api/v5/public/time';
      const response = await this.get<ApiResponse<Array<{ ts: string }>>>(endpoint);

      if (response && response.data && response.data.length > 0) {
        const serverTime = new Date(parseInt(response.data[0].ts));
        return {
          success: true,
          message: `连接成功，服务器时间: ${serverTime.toISOString()}`
        };
      }

      return { success: true, message: '连接成功' };
    } catch (error: any) {
      return {
        success: false,
        message: `连接失败: ${error.message || '未知错误'}`
      };
    }
  }

  /**
   * 获取历史K线数据
   * @param params 请求参数
   * @returns Promise 包含K线数据数组
   */
  async getHistoricalKlines(params: HistoricalKlineParams): Promise<KlineData[]> {
    const endpoint = '/api/v5/market/history-candles';

    // 转换参数
    const queryParams: Record<string, string> = {
      instId: params.instId
    };

    if (params.after) queryParams.after = params.after;
    if (params.before) queryParams.before = params.before;
    if (params.bar) queryParams.bar = params.bar;
    if (params.limit) queryParams.limit = params.limit.toString();

    const response = await this.get<ApiResponse<KlineData[]>>(endpoint, queryParams);
    return response.data;
  }

  /**
   * 获取并格式化历史K线数据
   * @param params 请求参数
   * @returns Promise 包含格式化后的K线数据数组
   */
  async getFormattedHistoricalKlines(params: HistoricalKlineParams): Promise<FormattedKlineData[]> {
    const klines = await this.getHistoricalKlines(params);

    return klines.map(kline => ({
      timestamp: parseInt(kline[0]),
      open: parseFloat(kline[1]),
      high: parseFloat(kline[2]),
      low: parseFloat(kline[3]),
      close: parseFloat(kline[4]),
      volume: parseFloat(kline[5]),
      volumeCcy: parseFloat(kline[6]),
      volumeCcyQuote: parseFloat(kline[7]),
      confirmed: kline[8] === '1'
    }));
  }

  /**
   * 分页获取所有历史K线数据
   * @param params 请求参数
   * @param maxResults 最大结果数量，默认为1000
   * @returns Promise 包含所有K线数据数组
   */
  async getAllHistoricalKlines(
    params: HistoricalKlineParams,
    maxResults: number = 1000
  ): Promise<KlineData[]> {
    // 预先分配足够大小的数组，避免频繁扩容
    const allKlines: KlineData[] = [];
    let currentParams = { ...params };
    const limit = params.limit ? parseInt(params.limit.toString()) : 100;
    const maxPages = Math.ceil(maxResults / limit);

    for (let page = 0; page < maxPages; page++) {
      if (allKlines.length >= maxResults) break;

      const klines = await this.getHistoricalKlines(currentParams);

      if (klines.length === 0) break;

      // 直接使用push而不是创建新数组
      allKlines.push(...klines);

      // 如果获取的数据少于请求的限制，说明没有更多数据了
      if (klines.length < limit) break;

      // 使用最后一条数据的时间戳作为下一页的 after 参数
      currentParams.after = klines[klines.length - 1][0];
    }

    // 如果数组超出了maxResults限制，直接在原数组上截取，避免创建新数组
    return allKlines.length > maxResults ? allKlines.slice(0, maxResults) : allKlines;
  }

  /**
   * 获取并处理K线数据
   * @param instId 交易对
   * @param barStr K线周期字符串
   * @param limit 数据条数限制
   * @returns 格式化后的技术分析用K线数据
   */
  async fetchKlineData(instId: string, barStr: string, limit: number = 5000) {
    // 将字符串周期转换为枚举类型
    const bar = this.stringToKlineBar(barStr);

    // 获取原始K线数据
    const params = {
      instId,
      bar,
      limit: Math.min(300, limit)  // OKX API单次最大为300条
    };

    const rawKlines = await this.getAllHistoricalKlines(params, limit);

    // 优化：直接在转换过程中对数据排序，减少中间步骤和临时数组
    // 创建一个指定大小的结果数组
    const formattedKlines: FormattedKlineData[] = new Array(rawKlines.length);

    // 首先将所有原始数据转换为格式化数据
    for (let i = 0; i < rawKlines.length; i++) {
      const kline = rawKlines[i];
      formattedKlines[i] = {
        timestamp: parseInt(kline[0]),
        open: parseFloat(kline[1]),
        high: parseFloat(kline[2]),
        low: parseFloat(kline[3]),
        close: parseFloat(kline[4]),
        volume: parseFloat(kline[5]),
        volumeCcy: parseFloat(kline[6]),
        volumeCcyQuote: parseFloat(kline[7]),
        confirmed: kline[8] === '1'
      };
    }

    // 数据排序（OKX返回的是降序，需要转为升序）
    formattedKlines.sort((a, b) => a.timestamp - b.timestamp);

    // 转换为技术分析格式
    return this.klineUtilsService.convertFormattedToTA(formattedKlines);
  }

  /**
   * 获取最新的K线数据
   * @param instId 交易对
   * @param barStr K线周期字符串
   * @returns 最新的K线数据，转换为TAKlineData格式
   */
  async fetchLatestKline(instId: string, barStr: string): Promise<FormattedKlineData | null> {
    try {
      // 转换为API需要的类型
      const bar = this.stringToKlineBar(barStr);

      // 获取最近的一条K线数据
      const params = {
        instId,
        bar: KlineBar.SECOND_1, // 使用1秒级K线获取最新数据
        limit: "1"  // 只获取一条
      };

      const rawKlines = await this.getHistoricalKlines(params);
      if (rawKlines.length === 0) {
        console.log(`没有找到${instId}的最新K线数据`);
        return null;
      }

      // 格式化K线数据
      const kline = rawKlines[0];
      const formattedKline = {
        timestamp: parseInt(kline[0]),
        open: parseFloat(kline[1]),
        high: parseFloat(kline[2]),
        low: parseFloat(kline[3]),
        close: parseFloat(kline[4]),
        volume: parseFloat(kline[5]),
        volumeCcy: parseFloat(kline[6]),
        volumeCcyQuote: parseFloat(kline[7]),
        confirmed: kline[8] === '1'
      };

      return formattedKline;
    } catch (error) {
      console.error(`获取最新K线数据失败: ${error}`);
      return null;
    }
  }

  /**
   * 将字符串周期转换为KlineBar枚举
   */
  private stringToKlineBar(barStr: string): KlineBar {
    const map: Record<string, KlineBar> = {
      '1m': KlineBar.MINUTE_1,
      '3m': KlineBar.MINUTE_3,
      '5m': KlineBar.MINUTE_5,
      '15m': KlineBar.MINUTE_15,
      '30m': KlineBar.MINUTE_30,
      '1h': KlineBar.HOUR_1,
      '1H': KlineBar.HOUR_1,
      '4h': KlineBar.HOUR_4,
      '4H': KlineBar.HOUR_4,
      '1d': KlineBar.DAY_1,
      '1D': KlineBar.DAY_1
    };

    if (!map[barStr]) {
      throw new Error(`不支持的K线周期: ${barStr}`);
    }

    return map[barStr];
  }

  /**
   * 获取指定日期范围内的所有历史K线数据
   * @param instId 产品ID
   * @param bar K线粒度
   * @param startDate 开始时间 (JS Date object)
   * @param endDate 结束时间 (JS Date object)
   * @returns Promise 包含指定范围内所有K线数据的数组
   */
  async getKlinesForDateRange(
    instId: string,
    bar: KlineBar,
    startDate: Date,
    endDate: Date
  ): Promise<KlineData[]> {
    const allKlines: KlineData[] = [];
    const startTime = startDate.getTime();
    const endTime = endDate.getTime();
    // 从结束时间开始，向前（获取更早的）分页获取数据
    let after = endTime.toString();

    while (true) {
      const params: HistoricalKlineParams = {
        instId,
        bar,
        after,
        limit: 300,
      };

      const klines = await this.getHistoricalKlines(params);

      // 如果没有数据返回，或者返回数据为空，则终止循环
      if (!klines || klines.length === 0) {
        break;
      }

      // API返回的数据是时间降序的（新的在前）。
      // 获取这批数据中最早一条的时间戳。
      const oldestTsInThisBatch = parseInt(klines[klines.length - 1][0]);

      for (const kline of klines) {
        const ts = parseInt(kline[0]);
        // 只添加在时间范围内的数据
        if (ts >= startTime) {
          allKlines.push(kline);
        }
      }

      // 如果这批数据中最早的一条已经早于我们的起始时间，
      // 说明我们已经获取了所有需要的数据，可以终止循环。
      if (oldestTsInThisBatch < startTime) {
        break;
      }

      // 更新分页游标，用于获取下一批（更早的）数据
      after = oldestTsInThisBatch.toString();
      
      // 如果API返回的数据量小于我们请求的数量，说明已经没有更早的数据了
      if (klines.length < 300) {
          break;
      }

      // 礼貌性延迟，避免触发API频率限制
      await new Promise(resolve => setTimeout(resolve, 300));
    }

    // 最后，将收集到的K线数据按时间升序排序
    return allKlines.sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
  }
} 